#include "stdafx.h"
#include <type_traits>
#include <iostream>
#include <utility>

namespace tests { namespace gcc_comparison {
    template<class T> struct h
    {
        virtual void ar() = 0;
        virtual void rr() = 0;
    };

    template <class T, class E=void, bool IsV=true>
    struct dci
    {
        virtual void qh(h<T>**) = 0;
    };

    template <class T, class E>
    struct dci<T, E, false> : public E
    {
        virtual void qh(h<T>**) = 0;
        using E::qh;
    };

    template <class T, class E = void>
    struct dc : public dci<T, E, std::is_void<E>::value>
    {
    };

    template <
        class T1=void,
        class T2=void,
        class T3=void
    > 
    struct e; 

    template<
        class T1
    > 
    struct e<T1> : public T1
    {
        using T1::qh;
    };

    template<
        class T1,
        class T2
    > 
    struct e<T1,T2> :public T1, public T2
    {
        using T1::qh;
        using T2::qh;
    };

    struct a1 : public dc<a1>
    {
        virtual void ca1()=0;
    };

    struct a2 : public dc<a2>
    {
        virtual void ca2()=0;
    };

    struct a3 : public dc<a3>
    {
        virtual void ca3()=0;
    };

    struct b : public dc<b, e<a1, a2>>
    {
        virtual void cb()=0;
    };

    struct c : public dc<c, e<b, a3>>
    {
        virtual void cc()=0;
    };

    struct d : public dc<d, e<c>>
    {
        virtual void cd()=0;
    };

    struct ci 
        : public c
        , public h<a1>, public h<a2>, public h<a3>
        , public h<b>, public h<c>
    {
        void ca1() {}
        void ca2() {}
        void ca3() {}
        void cb() {}
        void cc() {}
        void qh(h<a1>** h) {*h=this;}
        void qh(h<a2>**h) {*h=this;}
        void qh(h<a3>** h) {*h=this;}
        void qh(h<b>**h) {*h=this;}
        void qh(h<c>**h) {*h=this;}
        void ar(){}
        void rr(){}
    };

    struct di 
        : public d
        , public h<a1>, public h<a2>, public h<a3>
        , public h<b>, public h<c>, public h<d>
    {
        void ca1() {}
        void ca2() {}
        void ca3() {}
        void cb() {}
        void cc() {}
        void cd() {}
        void qh(h<a1>** h) {*h=this;}
        void qh(h<a2>**h) {*h=this;}
        void qh(h<a3>** h) {*h=this;}
        void qh(h<b>**h) {*h=this;}
        void qh(h<c>**h) {*h=this;}
        void qh(h<d>**h) {*h=this;}
        void ar(){}
        void rr(){}
    };

    using namespace Microsoft::VisualStudio::CppUnitTestFramework;

    TEST_CLASS(test_class)
    {
        VEX_TESTS_TRACKED_MEM_CHECK();
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.core.dual.gcc_comparison")
            END_TEST_CLASS_ATTRIBUTE();
    public:
        TEST_METHOD_INITIALIZE(set_up)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_START();
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_FINISH();
        }

        TEST_METHOD(type_relations_are_satisfied)
        {
            Assert::IsTrue(
                std::is_base_of<a1, b>::value &&
                std::is_base_of<a2, b>::value &&
                std::is_base_of<a3, c>::value &&
                std::is_base_of<b, c>::value);
        }

        TEST_METHOD(c_can_select_qh_from_bases)
        {
            typedef decltype(std::declval<c*>()->qh(std::declval<h<a1>**>())) r;

            Assert::IsTrue(std::is_void<r>::value);
        }

        TEST_METHOD(c_can_call_qh_from_bases)
        {
            ci cimpl;
            c* cp=&cimpl;

            h<a1>* ha1;
            h<a2>* ha2;
            h<a3>* ha3;
            h<b>* hb;
            h<c>* hc;
            cp->qh(&ha1);
            cp->qh(&ha2);
            cp->qh(&ha3);
            cp->qh(&hb);
            cp->qh(&hc);
        }

        TEST_METHOD(d_can_select_qh_from_bases)
        {
            typedef decltype(std::declval<d*>()->qh(std::declval<h<a1>**>())) r;

            Assert::IsTrue(std::is_void<r>::value);
        }

        TEST_METHOD(d_can_call_qh_from_bases)
        {
            di dimpl;
            d* dp=&dimpl;

            h<a1>* ha1;
            h<a2>* ha2;
            h<a3>* ha3;
            h<b>* hb;
            h<c>* hc;
            h<d>* hd;
            dp->qh(&ha1);
            dp->qh(&ha2);
            dp->qh(&ha3);
            dp->qh(&hb);
            dp->qh(&hc);
            dp->qh(&hd);
        }
    };
}}